#if 0
Linux字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open、close、read和write系统调用。
要自己申请设备号，注册到devfs中。
#endif
#include <linux/module.h>
#include <linux/init.h>

#include <linux/cdev.h>

// kmalloc
#include <linux/slab.h>
// file_operations
#include <linux/fs.h>
// copy_to_user copy_from_user
#include <linux/uaccess.h>

// mmap
#include <linux/mm.h>

#include "mydev.h"

struct mydev_dev *g_dev;
struct user_private_data* udata_array[MAX_USER] = {0};

static struct mydev_dev *mydev_dev_get(void )
{
    sc_debug("get");
    return g_dev;
}

static void mydev_dev_set(struct mydev_dev * pdev)
{
    sc_debug("set");
    g_dev = pdev;
}

// 实现每一个应用程序都对应自己一个私有数据
static ssize_t mydev_open(struct inode *inode, struct file *filp)
{
    struct mydev_dev *dev;
    struct user_private_data *udata = NULL;
#if 0
    dev = container_of(inode->i_cdev,
                       struct mydev_dev, cdev);
    // printk("%p\n", dev); same as init
#else
    dev = mydev_dev_get();
#endif
    if(dev == NULL)
    {
        return -EBUSY;
    }
    if(dev->user_cnt >= MAX_USER)
    {
        return EMFILE;
    }

#if 0
    // 如果需要针对每一个进程的私有数据，那么只需要参考下列代码即可
    udata = kmalloc(sizeof(struct user_private_data), GFP_KERNEL);
    if(udata == NULL) return -ENOMEM;
    printk("fops private data : %p\n", udata);

    udata->dev = dev;
    filp->private_data = udata;
    udata_array[dev->user_cnt] = udata;
    dev->user_cnt++;
#else
    filp->private_data = dev;
#endif
    return 0;
}

static ssize_t mydev_read(struct file *filp, char __user *buf,
                              size_t size, loff_t *poffset)
{
    int ret = 0;
    unsigned long p = *poffset;
    unsigned int count = size;
    unsigned long flags = 0;
#if 0
    struct user_private_data *udata = filp->private_data;
    struct mydev_dev *dev = udata->dev;
#else
    struct mydev_dev *dev = filp->private_data;
#endif

    printk("%p\n", dev);
    if(dev == NULL)
    {
        return -EBUSY;
    }
    sc_debug("count %u, poffset %p/%lu\n", count, poffset, p);
    sc_debug("ubuff %p\n", buf);

    if(p >=MEM_SIZE)
        goto end;
    sc_debug("wait_event_interruptible, maybe you need write something first\n");
    if(wait_event_interruptible(dev->r_wait,
                                dev->status == UD_READY))
    {
        sc_debug("abort from wait_event_interruptible\n");
        goto end;
    }
    sc_debug("normal after wait_event_interruptible\n");

    if(count > MEM_SIZE - p)
        count = MEM_SIZE -p;

    if(buf)
    if(copy_to_user(buf, dev-> mem + p, count)) {
        ret = -EFAULT;
        goto end;
    }

    *poffset +=count;
    ret = count;

    sc_info("Read %u bytes(s) from pos %lu\n", count, p);
    spin_lock_irqsave(&dev->spinlock, flags);
    dev->status = UD_BUSY;
    spin_unlock_irqrestore(&dev->spinlock, flags);
end:
    return ret;
}

static ssize_t mydev_write(struct file *filp, const char __user *buf,
                              size_t size, loff_t *poffset)
{
    unsigned long flags = 0;
    int ret = 0;
    unsigned long p = *poffset;
    unsigned int count = size;
#if 0
    struct user_private_data *udata = filp->private_data;
    struct mydev_dev *dev = udata->dev;
#else
    struct mydev_dev *dev = filp->private_data;
#endif

    sc_debug("count %u, poffset %p/%lu\n", count, poffset, p);
    sc_debug("ubuff %p\n", buf);

    if(p >=MEM_SIZE)
        goto end;

    if(count > MEM_SIZE - p)
        count = MEM_SIZE -p;

    if(buf)
    if(copy_from_user(dev-> mem + p, buf, count)) {
        ret = -EFAULT;
        goto end;
    }

    spin_lock_irqsave(&dev->spinlock, flags);
    *poffset +=count;
    ret = count;
    dev->status = UD_READY;
    spin_unlock_irqrestore(&dev->spinlock, flags);
    sc_info("Write %u bytes(s) from %lu\n", count, p);
end:
    return ret;
}

#if 0
static loff_t mydev_llseek(struct file *filp, loff_t offset, int whence)
{
    //struct mydev_dev *dev = filp->private_data;
    loff_t ret = 0;
    switch(whence)
    {
      case SEEK_SET: /* Begin */
        if(offset < 0) {
            goto err;
        }
        if((unsigned long long) offset > MEM_SIZE) {
            goto err;
        }
        filp->f_pos =  offset;
        break;
      case SEEK_CUR: /* Curr*/
        if(filp->f_pos + offset > MEM_SIZE)
            goto err;
        if(filp->f_pos + offset < 0)
            goto err;
        filp->f_pos += offset;
        break;
      case SEEK_END: /* End */
        if(filp->f_pos + offset < 0)
            goto err;
        filp->f_pos = MEM_SIZE - 1 + offset;
        break;
      default:
        goto err;
    }
    ret = filp->f_pos;

    return ret;
err:
    ret = -EINVAL;
    return ret;
}
#endif

static long mydev_ioctl(struct file *filp, unsigned int cmd,
                            unsigned long arg)
{
#if 0
    struct user_private_data *udata = filp->private_data;
    struct mydev_dev *dev = udata->dev;
#else
    struct mydev_dev *dev = filp->private_data;
#endif
    switch(cmd) {
    case GM_IOCTL_CMD_CLEAR:
        memset(dev->mem, 0, MEM_SIZE);
        sc_info("Mem reset as zer0.\n");
        return 0;
    default:
        return -EINVAL;
    }
}

// 在QEMU中进行的测试，好像有点问题
static int mydev_mmap(struct file *filp, struct vm_area_struct *vma)
{
#if 0
    struct user_private_data *udata = filp->private_data;
    struct mydev_dev *dev = udata->dev;
#else
    struct mydev_dev *dev = filp->private_data;
#endif
    // vma_pgoff表示的是该vma表示的区间在缓冲区中的偏移地址，单位是页。这个值是用户调用mmap时传入的最后一个参数，不过用户空间的offset的单位是字节（当然必须是页对齐），进入内核后，内核会将该值右移PAGE_SHIFT（12），也就是转换为以页为单位。因为要在打印这个编译地址，所以这里将其再左移PAGE_SHIFT，然后赋值给offset。
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
    // 计算内核缓冲区中将被映射到用户空间的地址对应的物理页帧号。virt_to_phys接受的虚拟地址必须在低端内存范围内，用于将虚拟地址转换为物理地址，而vmaloc返回的虚拟地址不在低端内存范围内，所以需要用专门的函数。
    //unsigned long pfn_start = (virt_to_phys(dev->mem) >> PAGE_SHIFT) + vma->vm_pgoff;
    unsigned long pfn_start =(unsigned long)dev->mem;
    // 计算内核缓冲区中将被映射到用户空间的地址对应的虚拟地址
    unsigned long virt_start = (unsigned long)dev->mem + offset;
    // 计算该vma表示的内存区间的大小
    unsigned long size = vma->vm_end - vma->vm_start;
    int ret = 0;

    sc_info("phy: 0x%lx, offset: 0x%lx, size: 0x%lx, mmap\n", pfn_start << PAGE_SHIFT, offset, size);

    // 调用remap_pfn_range将物理页帧号pfn_start对应的物理内存映射到用户空间的vm->vm_start处，映射长度为该虚拟内存区的长度。由于这里的内核缓冲区是用kzalloc分配的，保证了物理地址的连续性，所以会将物理页帧号从pfn_start开始的（size >> PAGE_SHIFT）个连续的物理页帧依次按序映射到用户空间。
    ret = remap_pfn_range(vma,		    /* 映射虚拟内存空间 */
						  vma->vm_start,/* 映射虚拟内存空间起始地址 */
						  pfn_start,    /* 与物理内存对应的页帧号，物理地址右移12位 */
						  size,         /* 映射虚拟内存空间大小,页大小的整数倍 */
                          vma->vm_page_prot);/* 保护属性 */
    if (ret)
        printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]\n",
            __func__, vma->vm_start, vma->vm_end);
    else
        printk("%s: map 0x%lx to 0x%lx, size: 0x%lx\n", __func__, virt_start,
            vma->vm_start, size);

    return ret;
}

int mydev_release(struct inode *inode, struct file *filp)
{
#if 0
    struct user_private_data *udata = filp->private_data;
    struct mydev_dev *dev = udata->dev;
#else
    struct mydev_dev *dev = filp->private_data;
#endif
#if 0
    struct user_private_data *per;
    int i;
    for(i = 0; i < dev->user_cnt; i++)
    {
        per = udata_array[i];
        printk("fops private data : %p\n", per);
        kfree(per);
    }
#endif
    dev->user_cnt = 0;
    return 0;
}

static struct file_operations mydev_fops = {
    .owner =            THIS_MODULE,
    .open =             mydev_open,
    .read =             mydev_read,
    .write =            mydev_write,
    .unlocked_ioctl =   mydev_ioctl,
    .mmap =             mydev_mmap,
    .release =          mydev_release,
#if 0
    .llseek =           mydev_llseek,
#endif
};

static struct mydev_dev *mydev_setup_dev(void)
{
    static struct mydev_dev *pdev = NULL;
    int err;
    int devno;

    if(pdev) return pdev;

    err = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);
    if(err)
    {
        sc_err("Error when alloc_chrdev_region, %d\n", err);
        goto fail;
    }

    pdev = kmalloc(sizeof(*pdev), GFP_KERNEL);
    if(!pdev)
        goto fail_malloc;
    pdev->devno = devno;

    cdev_init(&pdev->cdev, &mydev_fops);
    pdev->cdev.owner = THIS_MODULE;
    err = cdev_add(&pdev->cdev, devno, 1);
    if(err)
        goto fail_cdev_add;
    pdev->class = class_create(THIS_MODULE, DEV_NAME);
    device_create(pdev->class, NULL, devno, NULL, DEV_NAME);

    return pdev;

fail_cdev_add:
    sc_err("Failed when [cdev_add]\n");
fail_malloc:
    unregister_chrdev_region(devno, 1);

fail:
    return NULL;
}

static void mydev_desetup_dev(void)
{
    struct mydev_dev *pdev = mydev_dev_get();

    cdev_del(&pdev->cdev);

    unregister_chrdev_region(pdev->devno,
                             1);

    device_destroy(pdev->class, pdev->devno);

    /* 删除类 */
    class_destroy(pdev->class);

    kfree(pdev);
}

static int __init mydev_init(void)
{
    struct mydev_dev *pdev;
    int err;

    sc_info("init\n");

    pdev = mydev_setup_dev();
    if(!pdev)
    {
        err =  -ENOMEM;
        goto err;
    }

    printk("%p\n", pdev);
    mydev_dev_set(pdev);
    pdev->user_cnt = 0; // schips
    // for block io
    init_waitqueue_head(&pdev->r_wait);
    spin_lock_init(&pdev->spinlock);
    pdev->status = UD_BUSY;

    sc_info("Create '/dev/%s'\n", DEV_NAME);

    return 0;
err:
    return err;
}

static void __exit mydev_exit(void)
{
    sc_info("Exit\n");
    mydev_desetup_dev();
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_AUTHOR("Schips");
MODULE_DESCRIPTION("My devices for fops");
MODULE_LICENSE("GPL");
